﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using Microsoft.Ccr.Core;
using XcoAppSpaces.Contracts.CcrExtensions;
using XcoAppSpaces.Contracts.CcrWiring;
using XcoAppSpaces.Contracts.Messaging;
using XcoAppSpaces.Core.CcrWiring.CcrRemote;
using XcoAppSpaces.Contracts.CcrDispatching;

namespace XcoAppSpaces.Core.CcrWiring
{
	/// <summary>
	/// Service for wiring workers, worker proxies and remote ports with the Ccr
	/// </summary>
	public class CcrWirer : ICcrWirer
	{
		private readonly WorkerFactory wf;
		private ICcrDispatcherService dispatcherService; //the dispatcher service that is used for wiring, if no other dispatcherqueue is given

		/// <summary>
		/// Creates a new CcrWirer.
		/// </summary>
		public CcrWirer()
		{
			wf = new WorkerFactory();
		}

		/// <summary>
		/// Gets the ccr dispatcher service from the space's service registry, which is needed
		/// to activate new ccr tasks for workers, worker proxies and remote ports.
		/// </summary>
		/// <param name="serviceRegistry">The registry where the service of the space instance are registered</param>
		public void Initialize(XcoAppSpaces.Contracts.Service.IXcoServiceRegistry serviceRegistry)
		{
			dispatcherService = serviceRegistry.Resolve<ICcrDispatcherService>();
		}

		/// <summary>
		/// Creates a new Worker and wires the worker ports with the message handlers contained in the worker class.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker.</typeparam>
		/// <typeparam name="TImplementation">The implementation type of the worker.</typeparam>
		/// <param name="dpq">The dispatcher queue that should be used for wiring the worker, or null if space's the default dispatcher queue should be used.</param>
		/// <returns>A WorkerInfo object containing the worker instance and additional information that has been gathered during the worker wiring.</returns>
		public WorkerInfo<TMessageContract> CreateWorker<TMessageContract, TImplementation>(DispatcherQueue dpq)
			where TMessageContract : IPort
			where TImplementation : TMessageContract, new()
		{
			return wf.Create<TMessageContract, TImplementation>(dpq ?? dispatcherService.DispatcherQueue);
		}

		/// <summary>
		/// Wires a given worker's ports with its message handlers.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker.</typeparam>
		/// <param name="worker">The worker instance.</param>
		/// <param name="dpq">The dispatcher queue that should be used for wiring the worker, or null if space's the default dispatcher queue should be used.</param>
		/// <returns>A WorkerInfo object containing the worker instance and additional information that has been gathered during the worker wiring.</returns>
		public WorkerInfo<TMessageContract> WireWorker<TMessageContract>(TMessageContract worker, DispatcherQueue dpq)
			where TMessageContract : IPort
		{
			return wf.Wire<TMessageContract>(worker, dpq ?? dispatcherService.DispatcherQueue);
		}

		/// <summary>
		/// Creates a worker proxy for a remote worker from the given parameters.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker (must have an empty constructor so it can be instantiated as a proxy).</typeparam>
		/// <param name="proxyInfo">Information that is needed to create the worker proxy, like name and address of the worker.</param>
		/// <returns>The created worker proxy.</returns>
		public TMessageContract CreateWorkerProxy<TMessageContract>(WorkerProxyInfo proxyInfo)
			where TMessageContract : IPort, new()
		{
			var workerProxy = new TMessageContract();
			{
				var task = new NamedRemotableTask(proxyInfo.MessageProcessor, proxyInfo.WorkerAddress, proxyInfo.CommServiceName, proxyInfo.WorkerIdentifier, proxyInfo.SessionID, workerProxy, MessageTransferMode.Parallel);
				workerProxy.ReceiveWithRemotableTask(dispatcherService.InternalDispatcherQueue, task, proxyInfo.ProcessorAttributes);
			}
			return workerProxy;
		}

		/// <summary>
		/// Removes all registered receiver tasks from all ports of the worker.
		/// </summary>
		/// <param name="worker">The worker that should be unwired.</param>
		public void UnwireWorker(IPort worker)
		{
			worker.UnregisterAllReceiverTasks();
		}

		/// <summary>
		/// Gets a serialization info object that contains all information that is needed by the receiving space
		/// to deserialize the worker. The current serialization context is used to get the local adress, and
		/// to register the port at the local space and get a unique id for the port, if it is not already
		/// a remote port.
		/// </summary>
		/// <param name="port">The port to be serialized.</param>
		/// <returns>Object containing the info that must be sent to the remote space for deserialization of the port.</returns>
		public XcoPortSerializationInfo GetPortSerializationInfo(IPort port)
		{
			var portInfo = port.GetPortSerializationInfo();
			if (!portInfo.IsRemote)
			{
				portInfo.MakeRemote(SerializationContext.Current.LocalAddress, SerializationContext.Current.RegisterPort(port));
			}
			return portInfo;
		}

		/// <summary>
		/// Wires a port using the given serialization info, so that it is ready for remote communication. The
		/// current serialization context is used to get the name of the communication service that should be
		/// used to send messages to the original port.
		/// </summary>
		/// <param name="port">The port to be wired.</param>
		/// <param name="portSerializationInfo">Info that is needed for wiring the remote port, like the port's id and remote address.</param>
		public void WireRemotePort(IPort port, XcoPortSerializationInfo portSerializationInfo)
		{
			var task = new AnonymousRemotableTask(
				SerializationContext.Current.MessageProcessor,
				portSerializationInfo.RemoteAddress,
				SerializationContext.Current.CurrentCommServiceName,
				portSerializationInfo.RemotePortGuid,
				port,
				MessageTransferMode.Sequential); //remote ports are currently always running with sequential transfer mode

			port.ReceiveWithRemotableTask(dispatcherService.InternalDispatcherQueue, task);
		}
	}
}
